package com.tang.plugin.ui;

import com.intellij.icons.AllIcons;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.packageDependencies.ui.TreeModel;
import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.treeStructure.Tree;
import com.tang.plugin.model.FileTreeNode;
import com.tang.plugin.model.Module;
import com.tang.plugin.utils.PopMenuUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class PreviewPanel extends JPanel {

    private final Tree fileTree = new Tree();
    private Module module;
    private boolean replacePlaceHolder = true;
    private OnTreeUpdateListener listener;

    private final ColoredTreeCellRenderer treeCellRenderer = new ColoredTreeCellRenderer() {
        @Override
        public void customizeCellRenderer(@NotNull JTree jTree, Object o, boolean b, boolean b1, boolean b2, int i, boolean b3) {
            if (o instanceof DefaultMutableTreeNode) {
                Object userObject = ((DefaultMutableTreeNode) o).getUserObject();
                if (userObject instanceof FileTreeNode) {
                    if (replacePlaceHolder) {
                        this.append(((FileTreeNode) userObject).getRealName());
                    } else {
                        this.append(((FileTreeNode) userObject).getName());
                    }
                    if (((FileTreeNode) userObject).isDir()) {
                        setIcon(AllIcons.Nodes.Package);
                    } else {
                        setIcon(getIconByFileName(((FileTreeNode) userObject).getName()));
                    }
                }
            }
        }
    };

    private Icon getIconByFileName(String name) {
        String[] split = name.split("\\.");
        Icon icon = FileTypeManager.getInstance().getFileTypeByExtension(split[split.length - 1]).getIcon();
        if (icon == null) {
            return AllIcons.FileTypes.Text;
        }
        return icon;
    }

    public PreviewPanel() {
        setLayout(new BorderLayout());
        JBScrollPane pane = new JBScrollPane();
        pane.setViewportView(fileTree);
        add(pane, BorderLayout.CENTER);
        initPane();
    }

    private void initPane() {
        fileTree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON3) {
                    int row = fileTree.getRowForLocation(e.getX(), e.getY());
                    if (row != -1) {
                        DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent();
                        Object[] nodes = treeNode.getUserObjectPath();
                        if (nodes == null || nodes.length == 0 || !(nodes[0] instanceof FileTreeNode)) {
                            return;
                        }
                        FileTreeNode selectedNode = (FileTreeNode) nodes[nodes.length - 1];
                        showEditMenu(e, selectedNode);
                    }
                }
            }
        });
        fileTree.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_DELETE) {
                    DefaultMutableTreeNode[] selectedNodes = fileTree.getSelectedNodes(DefaultMutableTreeNode.class, null);
                    for (DefaultMutableTreeNode it : selectedNodes) {
                        if (it.getUserObject() instanceof FileTreeNode) {
                            ((FileTreeNode) (it.getUserObject())).removeFromParent();
                            it.removeFromParent();
                        }
                        updateFileTreeUI();
                    }
                }
            }
        });
        fileTree.setEditable(true);
        fileTree.setCellRenderer(treeCellRenderer);
    }

    private void showEditMenu(MouseEvent anchor, FileTreeNode current) {
        Map<String, PopMenuUtils.MenuCallback> map = new HashMap<>();
        if (current.isDir()) {
            map.put("New Directory", () -> FileDialog.showForCreate(current, true, fileTreeNode -> addTreeNode(current, fileTreeNode)));
            map.put("New File", () -> FileDialog.showForCreate(current, false, fileTreeNode -> addTreeNode(current, fileTreeNode)));
        }
        if (!current.isRoot()) {
            DefaultMutableTreeNode lastSelectedPathComponent = (DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent();
            map.put("Rename", () -> FileDialog.showForRefactor(current, fileTreeNode -> {
                lastSelectedPathComponent.setUserObject(fileTreeNode);
                updateFileTreeUI();
            }));
            map.put("Delete", () -> FileDialog.showForRefactor(current, fileTreeNode -> {
                if (current.removeFromParent()) {
                    lastSelectedPathComponent.removeFromParent();
                    if (lastSelectedPathComponent.getUserObject() instanceof FileTreeNode) {
                        ((FileTreeNode) lastSelectedPathComponent.getUserObject()).removeFromParent();
                    }
                    updateFileTreeUI();
                }
            }));
        }
        PopMenuUtils.show(anchor, map);
    }

    private void addTreeNode(FileTreeNode parent, FileTreeNode node) {
        parent.addChild(node, true);
        module.getTemplate().addPlaceholders(node.getPlaceholderInNodeName().toArray(new String[0]));
        module.getTemplate().addPlaceholders(node.getPlaceHolderMap());
        module.getTemplate().addFileTemplates(node.getFileTemplate());
        node.getFileTemplate().clear();
        node.getPlaceHolderMap().clear();
        ((DefaultMutableTreeNode) (fileTree.getLastSelectedPathComponent())).add(new DefaultMutableTreeNode(node));
        updateFileTreeUI();
    }

    public void setModuleConfigPreview(Module selectedModule) {
        Module copy = selectedModule.copyModule();
        copy.getTemplate().build();
        setModuleConfig(copy);
    }

    public void setModuleConfig(Module module) {
        this.module = module;
        fileTree.setModel(getTreeModel(this.module.getTemplate()));
        fileTree.doLayout();
        fileTree.updateUI();
        expandAll(fileTree, new TreePath(fileTree.getModel().getRoot()), true);
    }

    private TreeModel getTreeModel(FileTreeNode fileTreeNode) {
        return new TreeModel(getTree(fileTreeNode));
    }

    private DefaultMutableTreeNode getTree(FileTreeNode fileTreeNode) {
        DefaultMutableTreeNode result = new DefaultMutableTreeNode(fileTreeNode, true);
        if (fileTreeNode.isDir()) {
            fileTreeNode.getChildren().forEach((it) -> result.add(getTree(it)));
        }
        return result;
    }

    private void expandAll(JTree fileTree, TreePath parent, boolean expand) {
        TreeNode node = (TreeNode) (parent.getLastPathComponent());
        if (node.getChildCount() >= 0) {
            Enumeration<? extends TreeNode> children = node.children();
            while (children.hasMoreElements()) {
                TreeNode treeNode = children.nextElement();
                TreePath treePath = parent.pathByAddingChild(treeNode);
                expandAll(fileTree, treePath, expand);
            }
        }
        if (expand) {
            fileTree.expandPath(parent);
        } else {
            fileTree.collapsePath(parent);
        }
    }

    public void setOnTreeUpdateListener(OnTreeUpdateListener listener) {
        this.listener = listener;
    }

    private void updateFileTreeUI() {
        fileTree.updateUI();
        if (listener != null) {
            listener.invoke();
        }
    }

    public void setReplacePlaceholder(boolean selected) {
        if (selected != replacePlaceHolder) {
            replacePlaceHolder = selected;
            fileTree.updateUI();
        }
    }

    public interface OnTreeUpdateListener {
        void invoke();
    }

}
